﻿

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class Maze
{

    // 斜线单位距离
    public const int OBLIQUE = 14;
    // 直线单位距离
    public const int STEP = 10;
    // 地图数据
    public int[,] MazeArray { get;private set; }

    // 是否可以斜线走
    public bool isCanSlanting = true;

    private List<Point> CloseList;
    private List<Point> OpenList;

    public Maze(int[,] maze)
    {
        this.MazeArray = maze;
        OpenList = new List<Point>(this.MazeArray.Length);
        CloseList = new List<Point>(this.MazeArray.Length);
    }

    /// <summary>
    /// 寻找路径
    /// </summary>
    /// <param name="start">起始点</param>
    /// <param name="end">终点</param>
    /// <param name="isIgnoreCorner">是否忽略拐角障碍</param>
    /// <param name="isCloset">无法到达是否寻找目标最近点</param>
    /// <returns></returns>
    public Point FindPath(Point start, Point end, bool isIgnoreCorner,bool isCloset=true)
    {
        OpenList.Add(start);
        //未找到终点可以造可抵达的H最小的点
        Point minPointH = null;
        while (OpenList.Count!=0)
        {
            //找出F值最小的点
            var tempStart = OpenList.MinPoint();
            OpenList.RemoveAt(0);
            CloseList.Add(tempStart);
            //找出它相邻的点
            var surroundPoints = this.SurroundPoints(tempStart, isIgnoreCorner);
            int count = surroundPoints.Count;
            for (int i = 0; i < count; i++)
            {
                var point = surroundPoints[i];
                if (OpenList.Exists(point))
                    //计算G值, 如果比原来的大, 就什么都不做, 否则设置它的父节点为当前点,并更新G和F
                    FoundPoint(tempStart, point);
                else
                {
                    //如果它们不在开放列表里, 就加入, 并设置父节点,并计算GHF
                    NotFoundPoint(tempStart, end, point);
                    if (isCloset)
                    {
                        if (minPointH == null)
                            minPointH = point;
                        else if (point.H < minPointH.H)
                            minPointH = point;
                    }
                }
            }
            if (OpenList.Get(end) != null)
                return OpenList.Get(end);
        }
        //未找到终点可以造可抵达的H最小的点
        if (minPointH!=null)
        {
            return minPointH;
        }
        return OpenList.Get(end);
    }

    void FoundPoint(Point tempStart, Point point)
    {
        var G = CalcG(tempStart, point);
        if (G < point.G)
        {
            point.ParentPoint = tempStart;
            point.G = G;
            point.CalcF();
        }
    }

    void NotFoundPoint(Point tempStart, Point end, Point point)
    {
        point.ParentPoint = tempStart;
        point.G = CalcG(tempStart, point);
        point.H = CalcH(end, point);
        point.CalcF();
        OpenList.Add(point);
    }
    int CalcG(Point start, Point point)
    {
        //怀疑作者的写错用下面的
        // int G = (Math.Abs(point.X - start.X)+Math.Abs(point.Y-start.Y))==2? STEP : OBLIQUE; 
        int G = (Math.Abs(point.X - start.X)+Math.Abs(point.Y-start.Y))==1 ? STEP : OBLIQUE;
        int parentG = point.ParentPoint != null ? point.ParentPoint.G : 0;
        return G + parentG;
    }

    int CalcH(Point end, Point point)
    {
        int step = Math.Abs(point.X - end.X) + Math.Abs(point.Y - end.Y);
        return step * STEP;
    }

    /// <summary>
    /// 获取某个点周围可以到达的点
    /// </summary>
    /// <param name="point"></param>
    /// <param name="isIgnoreCorner"></param>
    /// <returns></returns>
    public List<Point> SurroundPoints(Point point, bool isIgnoreCorner)
    {
       
        var surroundPoints = new List<Point>();
        for (int x = point.X - 1; x <= point.X + 1; x++)
        {
            for (int y = point.Y - 1; y <= point.Y + 1; y++)
            {
                if(!isCanSlanting)
                    if (Math.Abs(x - point.X) + Math.Abs(y - point.Y) != 1)
                        continue;
                if(CanReach(point,x,y,isIgnoreCorner))
                    surroundPoints.Add(x,y);
            }
        }

        return surroundPoints;
    }

    /// <summary>
    /// 在二维数组对应的位置不为障碍物
    /// </summary>
    /// <param name="x"></param>
    /// <param name="y"></param>
    /// <returns></returns>
    private bool CanReach(int x, int y)
    {
        return this.MazeArray[x, y] == 0;
    }

    public bool CanReach(Point start, int x, int y, bool isIgnoreCorner)
    {
        if (!CanReach(x, y) || CloseList.Exists(x, y))
            return false;
        else
        {
            if (Math.Abs(x - start.X) + Math.Abs(y - start.Y) == 1)
                return true;
            //如果是斜方向移动, 判断是否 "拌脚"
            else
            {
                if (CanReach(Math.Abs(x - 1), y) && CanReach(x, Math.Abs(y - 1)))
                    return true;
                else
                    return isIgnoreCorner;
            }
        }
    }
}